#pragma once

#include "transform.h"
#include "sampler.h"
#include "film.h"

namespace kp {

	class Camera {
	public:
		virtual float generateRay(const CameraSample& sample, Ray& ray)const = 0;
	};

	class ProjectiveCamera : public Camera {
	public:
		ProjectiveCamera(const Transform& camToWorld, const Transform& proj, const BBox2f& screenWindow,
			float sopen, float sclose, float lensR, float focald, const Film& film) {
			// Initialize depth of field parameters
			lensRadius = lensR;
			focalDistance = focald;
			camToScreen = proj;
			// Compute projective camera screen transformations
			screenToRaster =
				Mat4::scale({ (float)film.size.w, (float)film.size.h, 1.0f }) *
				Mat4::scale({
				1.0f / (screenWindow.pMax.x - screenWindow.pMin.x),
				1.0f / (screenWindow.pMin.y - screenWindow.pMax.y),
				1.0f }) *
				Mat4::translate({ -screenWindow.pMin.x, -screenWindow.pMax.y, 0 });
			// Compute projective camera transformations
			rasterToScreen = screenToRaster.inverse();
			rasterToCam = camToScreen.inverse() * rasterToScreen;
		}

		virtual float generateRay(const CameraSample& sample, Ray& ray)const = 0;

	protected:
		Transform camToScreen, rasterToCam;
		Transform screenToRaster, rasterToScreen;
		Float lensRadius, focalDistance;
	};

	class PerspectiveCamera : public ProjectiveCamera {
	public:
		PerspectiveCamera(const Transform& camToWorld, const BBox2f& screenWindow,
			float sopen, float sclose, float lensR, float focald, float fov, const Film& film)
			: ProjectiveCamera(camToWorld, Mat4::perspective(fov, 1e-2f, 1000.0f),
				screenWindow, sopen, sclose, lensR, focald, film),
			cameraToWorld(camToWorld)
		{}

		virtual float generateRay(const CameraSample& sample, Ray& ray) const override {
			// Generate raster and camera sampler
			Point3f Pras(sample.pFilm.x, sample.pFilm.y, 0.f);
			Point3f Pcam = rasterToCam(Pras);
			ray = Ray(Point3f(0.0f, 0.0f, 0.0f), Vec3f(Pcam).normalized());
			ray.mint = 0.001f;
			ray.maxt = MaxFloat;
			cameraToWorld(ray, ray);
			return 1.0f;
		}

	private:
		Transform cameraToWorld;

	};

	class OrthographicCamera : public ProjectiveCamera {
	public:
		OrthographicCamera(const Transform& camToWorld, const BBox2f& screenWindow,
			float sopen, float sclose, float lensR, float focald, const Film& film)
			: ProjectiveCamera(camToWorld, Mat4::orthographic(1e-2f, 1000.0f),
				screenWindow, sopen, sclose, lensR, focald, film)
		{
			cameraToWorld = camToWorld;
		}

		virtual float generateRay(const CameraSample& sample, Ray& ray) const override {
			return 1.0f;
		}

	private:
		Transform cameraToWorld;// , worldToCamera;

	};

}
